package com.example.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.common.core.enums.ResultCode;
import com.example.common.core.constants.Constants;
import com.example.common.security.exception.ServiceException;
import com.example.system.domain.exam.Exam;
import com.example.system.domain.exam.ExamQuestion;
import com.example.system.domain.exam.dto.ExamAddDTO;
import com.example.system.domain.exam.dto.ExamEditDTO;
import com.example.system.domain.exam.dto.ExamQueryDTO;
import com.example.system.domain.exam.dto.ExamQuestAddDTO;
import com.example.system.domain.exam.vo.ExamDetailVO;
import com.example.system.domain.exam.vo.ExamVO;
import com.example.system.domain.question.Question;
import com.example.system.domain.question.vo.QuestionVO;
import com.example.system.manager.ExamCacheManager;
import com.example.system.mapper.exam.ExamMapper;
import com.example.system.mapper.exam.ExamQuestionMapper;
import com.example.system.mapper.question.QuestionMapper;
import com.example.system.service.exam.IExamService;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;

@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements IExamService {
    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        return examMapper.selectExamList(examQueryDTO);
    }

    @Override
    public String add(ExamAddDTO examAddDTO) {
        // 判断是否已经存在
        checkExamSaveParams(examAddDTO, null);
        Exam exam = new Exam();
        BeanUtil.copyProperties(examAddDTO, exam);
        examMapper.insert(exam);
        return exam.getExamId().toString();
    }

    private void checkExamSaveParams(ExamAddDTO examSaveDTO, Long examId) {
        // 添加竞赛，编辑竞赛时候参数合法性校验
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examSaveDTO.getTitle())
                .ne(examId != null, Exam::getExamId, examId));
        if (CollectionUtil.isNotEmpty(examList)) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }
        if (examSaveDTO.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);
        }
        if (examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }

    @Override
    public boolean addQuestion(ExamQuestAddDTO examQuestAddDTO) {
        // 竞赛是否存在
        Exam exam = getExam(examQuestAddDTO.getExamId());
        checkExam(exam);
        if(Constants.TRUE.equals(exam.getStatus())){
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        // 题目集合为空
        Set<Long> questionIdSet = examQuestAddDTO.getQuestionIdSet();
        if (CollectionUtil.isEmpty(questionIdSet)) {
            return true;
        }
        // 判断多个题目是否全部存在
        List<Question> questions = questionMapper.selectBatchIds(questionIdSet);
        if (CollectionUtil.isEmpty(questions) || questions.size() < questionIdSet.size()) {
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }
        return saveExamQuestion(questionIdSet, exam);
    }

    @Override
    public ExamDetailVO getExamDetail(Long examId) {
        ExamDetailVO examDetailVO = new ExamDetailVO();
        Exam exam = getExam(examId);
        BeanUtil.copyProperties(exam, examDetailVO);
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId).orderByAsc(ExamQuestion::getQuestionOrder));
        if (CollectionUtil.isEmpty(examQuestionList)) {
            return examDetailVO;
        }
        List<Long> questionIdList = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();
        List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>().in(Question::getQuestionId, questionIdList)
                .select(Question::getQuestionId, Question::getTitle, Question::getDifficulty));
        // List<QuestionVO> questionVOList = new ArrayList<>();
        List<QuestionVO> questionVOList = BeanUtil.copyToList(questionList, QuestionVO.class);
        examDetailVO.setExamQuestionList(questionVOList);
        return examDetailVO;
    }

    @Override
    public int edit(ExamEditDTO examEditDTO) {
        Exam oldExam = getExam(examEditDTO.getExamId());
        checkExam(oldExam);
        if(Constants.TRUE.equals(oldExam.getStatus())){
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        // 修改后的标题不能重复
        // 修改后的开始时间小于结束时间，大于当前时间
        checkExamSaveParams(examEditDTO, examEditDTO.getExamId());
        oldExam.setTitle(examEditDTO.getTitle());
        oldExam.setStartTime(examEditDTO.getStartTime());
        oldExam.setEndTime(examEditDTO.getEndTime());
        return examMapper.updateById(oldExam);
    }

    @Override
    public int deleteQuestion(Long examId, Long questionId) {
        // 检查资源是否存在
        Exam exam = getExam(examId);
        checkExam(exam);
        if(Constants.TRUE.equals(exam.getStatus())){
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        ExamQuestion examQuestion = getExamQuestion(examId,questionId);
        return examQuestionMapper.deleteById(examQuestion.getExamQuestionId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 多出对数据库进行修改和删除需要添加事务
    public int detele(Long examId) {
        // 目标：删除tb_exam中存储的竞赛基本信息，删除tb_exam_question表中的竞赛题目关系记录
        // 检查竞赛是否存在
        Exam exam = getExam(examId);
        // 检查是否可以删除
        checkExam(exam);
        // 删除竞赛的题目
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        // 删除竞赛基本信息
        return examMapper.deleteById(examId);
    }

    private ExamQuestion getExamQuestion(Long examId, Long questionId) {
        ExamQuestion examQuestion = examQuestionMapper.selectOne(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
        if(examQuestion == null){
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        return examQuestion;
    }

    private boolean saveExamQuestion(Set<Long> questionIdSet, Exam exam) {
        int index = 0;
        //
        List<ExamQuestion> examQuestionSet = new ArrayList<>();
        for (Long questionId : questionIdSet) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(index++);
            examQuestionSet.add(examQuestion);
        }
        return saveBatch(examQuestionSet);
    }

    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        return exam;
    }

    private void checkExam(Exam exam){
        // 那些竞赛可以删除，为发布的可以删除，已发布的未开始的可以删除，正在进行的不可以删除，已完赛的可以删除吗
        if(exam.getStartTime().isBefore(LocalDateTime.now())){
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
    }

    @Override
    public int publish(Long examId) {
        // 发布竞赛的前提：
        // 1、竞赛处于未发布状态
        // 2、竞赛开始时间大于当前时间，合理的应该是给用户三到七天的报名时间，
        // 3、不能对进行中的或已结束的竞赛进行发布操作
        // 4、不得对没有题目的竞赛进行发布

        Exam exam = getExam(examId);
        if (exam.getStartTime().isBefore(LocalDateTime.now()) && exam.getEndTime().isAfter(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }
        //select count(0) from tb_exam_question where exam_id = #{examId}
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        if (count == null || count <= 0) {
            throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }
        exam.setStatus(Constants.TRUE);

        // 要将新发布的竞赛数据存储到redis   e:t:l  e:d:examId
        // 存储一份竞赛的详细信息，并在对应列表上存储竞赛ID
        examCacheManager.addCache(exam);
        return examMapper.updateById(exam);
    }

    @Override
    public int cancelPublish(Long examId) {
        Exam exam = getExam(examId);
        checkExam(exam);
        exam.setStatus(Constants.FALSE);
        // 从redis中删除竞赛基本信息
        // 从未完赛列表中删除竞赛ID
        examCacheManager.deleteCache(examId);
        return examMapper.updateById(exam);
    }
}
